{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import heapq\n",
    "from tqdm import tqdm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_path = './data/train.txt'\n",
    "\n",
    "centerlize = True  # centerlize the rating data or not\n",
    "\n",
    "train_lines = 0\n",
    "all_avg = 0\n",
    "item_rating= {}\n",
    "\n",
    "with open(data_path, 'r') as f:\n",
    "    lines = f.readlines()\n",
    "    user_id = None\n",
    "    train_lines = len(lines)\n",
    "    data = {}\n",
    "    for line in lines:\n",
    "        line = line.strip()\n",
    "        if '|' in line:  # user line\n",
    "            train_lines -= 1\n",
    "            if(user_id != None and centerlize == True):\n",
    "                avg = data[user_id]['sum'] / data[user_id]['num_ratings']\n",
    "                data[user_id]['ratings'].update({k: v - avg for k, v in data[user_id]['ratings'].items()})\n",
    "                data[user_id]['norm'] = sum(x**2 for x in data[user_id]['ratings'].values())**0.5\n",
    "\n",
    "            user_id, num_ratings = line.split('|')\n",
    "            user_id = int(user_id)\n",
    "            data[user_id] = {}\n",
    "            data[user_id]['num_ratings'] = int(num_ratings)\n",
    "            data[user_id]['ratings'] = {}\n",
    "            data[user_id]['sum'] = 0\n",
    "        else:  # rating line\n",
    "            item_id, score = map(int, line.split())\n",
    "            data[user_id]['ratings'][item_id] = score\n",
    "            data[user_id]['sum'] += score\n",
    "            all_avg += score\n",
    "            if item_id not in item_rating:\n",
    "                item_rating[item_id] = {'num': 0, 'sum': 0}\n",
    "            item_rating[item_id]['num'] += 1\n",
    "            item_rating[item_id]['sum'] += score\n",
    "    if centerlize == True:\n",
    "        avg = data[user_id]['sum'] / data[user_id]['num_ratings']\n",
    "        data[user_id]['ratings'].update({k: v - avg for k, v in data[user_id]['ratings'].items()})\n",
    "        data[user_id]['norm'] = sum(x**2 for x in data[user_id]['ratings'].values())**0.5\n",
    "\n",
    "all_avg /= train_lines\n",
    "lines = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "similarity = {}\n",
    "for i, (userid1, user1_data) in tqdm(enumerate(data.items()), total=len(data)):\n",
    "    similarity[userid1] = {}\n",
    "    for j, (userid2, user2_data) in enumerate(data.items()):\n",
    "        if i >= j:\n",
    "            continue\n",
    "        if  user1_data['norm'] == 0 or  user2_data['norm'] == 0:\n",
    "            similarity[userid1][userid2] = 0\n",
    "            continue\n",
    "        else:\n",
    "            cos_sim = 0.0\n",
    "            for item, rating in user1_data['ratings'].items():\n",
    "                if item in user2_data['ratings']:\n",
    "                    cos_sim += rating * user2_data['ratings'][item]\n",
    "            cos_sim = cos_sim / (user1_data['norm'] * user2_data['norm'])\n",
    "            similarity[userid1][userid2] = cos_sim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# save similarity matrix\n",
    "np.save('similarity.npy', similarity)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#similarity = np.load('similarity.npy', allow_pickle=True).item() # 如果保存了中间结果，可以直接读取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#read test data\n",
    "test_input = {}\n",
    "with open('../data/test.txt', 'r') as f:\n",
    "    lines = f.readlines()\n",
    "    for line in lines:\n",
    "        line = line.strip()  # 去除行尾的换行符\n",
    "        if '|' in line:\n",
    "            # 这是一个用户的开始\n",
    "            userid, num_items = line.split('|')  # 分割用户ID和评分项目数量\n",
    "            test_input[userid] = {'num_items': int(num_items), 'items': []}\n",
    "        else:\n",
    "            # 这是一个项目ID\n",
    "            itemid = line\n",
    "            test_input[userid]['items'].append(itemid)\n",
    "lines = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_top_N_keys(user_id, n, item_id = None, only_keys = True):\n",
    "    all_similarity = [(i, similarity[i][user_id]) for i in range(user_id)]\n",
    "    all_similarity.extend(list(similarity[user_id].items()))\n",
    "    if item_id == None: #不要求item_id在dictionary中\n",
    "        top_n_items = heapq.nlargest(n, all_similarity, key=lambda x: x[1])\n",
    "    else: \n",
    "        top_n_items = heapq.nlargest(n, ((k, v) for (k, v) in all_similarity if item_id in data[k]['ratings']), key=lambda item: item[1])\n",
    "    if only_keys:\n",
    "        top_n_keys = [key for (key, value) in top_n_items ]\n",
    "        return top_n_keys\n",
    "    else:\n",
    "        return top_n_items"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "evaluate done\n"
     ]
    }
   ],
   "source": [
    "#the oridinary version\n",
    "test_output = {}\n",
    "\n",
    "for user_id, user_data in (test_input.items()):\n",
    "    user_id = int(user_id)\n",
    "    test_output[user_id] = {}\n",
    "    for item_id in user_data['items']:\n",
    "        item_id = int(item_id)\n",
    "        eval = 0\n",
    "        sim_sum = 0\n",
    "        N_neighbor = find_top_N_keys(user_id, 10, item_id, only_keys=False)\n",
    "        for neighbor in N_neighbor:\n",
    "            if item_id in data[neighbor[0]]['ratings']:\n",
    "                sim_sum += neighbor[1]\n",
    "                eval += neighbor[1] * (data[neighbor[0]]['ratings'][item_id])\n",
    "        if sim_sum != 0:\n",
    "            eval /= sim_sum\n",
    "        eval += data[user_id]['sum']/data[user_id]['num_ratings']\n",
    "        eval = eval if eval <= 100 else 100\n",
    "        test_output[user_id][item_id] = int(eval) \n",
    "\n",
    "print('evaluate done')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "write done\n"
     ]
    }
   ],
   "source": [
    "result_path = 'result_normal.txt'\n",
    "count = 0\n",
    "with open(result_path, 'w') as f:\n",
    "    for userid, item_list in test_output.items():\n",
    "        f.write(f\"{userid}|6\\n\")\n",
    "        for item, rating in item_list.items():\n",
    "            f.write(f\"{item}  {rating}\\n\")\n",
    "\n",
    "print('write done')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
